package main;

import java.math.BigDecimal;
import java.math.MathContext;

/**
 * Expression class evaluates math expressions composed from real numbers and operators.
 * Currently supported operators: <b>+ - * /</b>
 * @author Joonas Vali
 *
 */
public class Expression {
	
	String value = null;	//the calculated value of the expression
	Expression first;		//first argument as another Expression
	Expression second;		//second argument as another Expression
	char operator;			//the operator between the expressions
	
	/**
	 * Preprocessing eliminates certain operator combos from the expression, replacing them with a shortcut.
	 * curretly replaces <b>+-</b> and <b>-+</b> combos with single <b>-</b> and <b>--</b> combos with a single <b>+</b>
	 * @param expression The expression to be preprocessed as a String
	 * @return The processed String
	 */
	private static String preProcess(String expression){
		// replaces +- or -+ with single -
		expression = expression.replaceAll("(\\+-)|(-\\+)", "-");
		// replaces -- with +
		expression = expression.replaceAll("(--)", "+");
		return expression;
	}
	
	/**
	 * Calculates the value of the provided expression.<br />
	 * Operators supported: + - * /
	 * @param expression The expression to be evaluated
	 * @return the value of the expression as a String
	 * @throws ArithmeticException if divided by zero
	 * @throws NumberFormatException on erroneous syntax 
	 */
	public static String calculate(String expression){
		return new Expression(preProcess(expression)).getValue();
	}
	
	/**
	 * Creates an Expression from the String
	 * @param expression The expression as a String
	 */
	private Expression(String expression){			
		if(expression.length() == 0){ 
			this.value = "0";
			return;
		}
		try{
			int index;
			if(( index = expression.lastIndexOf('-'))>-1 && index != 0){				
				// The expression will be evaluated as a sum with negative second argument.
				first = new Expression(expression.substring(0, index));
				second = new Expression(expression.substring(index, expression.length()));			
				operator = '+';				
			} else if((index = expression.lastIndexOf('+'))>-1){
				first = new Expression(expression.substring(0, index));
				second = new Expression(expression.substring(index+1, expression.length()));
				operator = '+';
			} else if((index = expression.lastIndexOf('*'))>-1){
				first = new Expression(expression.substring(0, index));
				second = new Expression(expression.substring(index+1, expression.length()));
				operator = '*';
			} else if((index = expression.lastIndexOf('/'))>-1){
				first = new Expression(expression.substring(0, index));
				second = new Expression(expression.substring(index+1, expression.length()));
				operator = '/';
			} 
			else {
				if(expression.equals("-0")) this.value = "0";
				else this.value = expression;
			}
		}catch(Exception e){e.printStackTrace();}
	}
	
	/**
	 * Get the value of the Expression.	  	
	 * Calculates, if not yet calculated.
	 * @return the value of the Expression.
	 * @throws ArithmeticException if divided by zero
	 * @throws NumberFormatException on erroneous syntax 
	 */
	public String getValue() throws ArithmeticException, NumberFormatException{		
		if(value != null) return value;
		else return evaluate();		
	}
	
	/**
	 * Calculate the value of the Expression
	 * @return the calculated value
	 * @throws ArithmeticException if divided by zero
	 * @throws NumberFormatException on erroneous syntax 
	 */
	private String evaluate() throws ArithmeticException, NumberFormatException{
		switch(operator){
			//case '-':				
				//value = new BigDecimal(first.getValue()).subtract(new BigDecimal(second.getValue())).toString();break;
			case '+':
				value = new BigDecimal(first.getValue()).add(new BigDecimal(second.getValue())).toString();break;
			case '*':
				value = new BigDecimal(first.getValue()).multiply(new BigDecimal(second.getValue())).toString();break;
			case '/':
				value = new BigDecimal(first.getValue()).divide(new BigDecimal(second.getValue()), MathContext.DECIMAL128).toString();break;
		}
		return value;
	}	
}
